// ============================================================================
// FileName: SIPRegistrationAgent.cs
//
// Description:
// Registration agent daemon to maintain SIP registrations with multiple SIP
// Registrar servers.
//
// Author(s):
// Aaron Clauson
//
// History:
// 13 Nov 2006	Aaron Clauson	Created.
//
// License: 
// This software is licensed under the BSD License http://www.opensource.org/licenses/bsd-license.php
//
// Copyright (c) 2006 Aaron Clauson (aaronc@blueface.ie), Blue Face Ltd, Dublin, Ireland (www.blueface.ie)
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are permitted provided that 
// the following conditions are met:
//
// Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 
// Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following 
// disclaimer in the documentation and/or other materials provided with the distribution. Neither the name of Blue Face Ltd. 
// nor the names of its contributors may be used to endorse or promote products derived from this software without specific 
// prior written permission. 
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, 
// BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
// IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, 
// OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, 
// OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
// POSSIBILITY OF SUCH DAMAGE.
// ============================================================================

using System;
using System.Collections.Generic;
using System.Data;
using System.Net;
using System.Runtime.Serialization;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using mimosa.Net;
using mimosa.SIP;
using mimosa.SIP.Transactions;
using mimosa.Crypto;
using mimosa.Utilities;
using mimosa.Authentication;
using log4net;

#if UNITTEST
using NUnit.Framework;
#endif

namespace mimosa.SIPServer
{
    public class UserRegistration
    {
        public const int DEFAULT_REGISTRATION_EXPIRY = 3600;
        public const int MINIMUM_ALLOWED_EXPIRY = 60;
        public const string CONTACT_ID_KEY = "switchtag";

        private static ILog logger = log4net.LogManager.GetLogger("sipproxy");
        
        public Guid RegistrationId = Guid.Empty;

        public string Owner;       // The username of the user the registration is being maintained for.
        public string Username;
        public string Password;
        public string Registrar;
        public string Realm;        // The domain/realm for the REGISTER request can be different from the SIP server the request gets sent to.
        public SIPURI Contact;     // Contact being maintained by this registration job.
        public List<SIPContactHeader> ContactsList = new List<SIPContactHeader>();  // List of contacts reported back from the registrar server.
        public DateTime LastRegisterTime = DateTime.MinValue;
        public bool Registered = false;
        public int ExpirySeconds = DEFAULT_REGISTRATION_EXPIRY;
        public int OriginalExpirySeconds = DEFAULT_REGISTRATION_EXPIRY;
        public IPEndPoint RegistrarIPEndPoint = null;
        public int CSeq = 0;                    // The SIP Header CSeq used in requests to the Registrar server.
        public string CallId;                   // The SIP Header CallId used in requests to the Registrar server.
        public DateTime LastRegisterRequestSent = DateTime.MinValue;
        public DateTime LastRegisterResponseRecevied = DateTime.MinValue;
        public bool AuthRequestSent = false;    // Only one additional request should be sent for each server response requiring authentiation.
        public bool DeleteRequired = false;     // Set to true if this registration should be removed.

        public UserRegistration(
            Guid registrationId,
            string owner,
            string username,
            string password,
            string registrar,
            string realm,
            SIPURI contact,
            int expiry)
        {
            RegistrationId = registrationId;
            Owner = owner;
            Username = username;
            Password = password;
            Registrar = registrar;
            Realm = realm;
            ExpirySeconds = expiry;
            OriginalExpirySeconds = ExpirySeconds;

            if (Realm == null || Realm.Trim().Length == 0)
            {
                Realm = Registrar;
            }

            Contact = contact;

            Contact.Parameters.Set(CONTACT_ID_KEY, Crypto.Crypto.GetRandomInt(6).ToString());
        }

        public UserRegistration(DataRow regRow)
        {
            RegistrationId = new Guid(regRow["registrationid"] as string);
            Owner = regRow["username"] as string;
            Username = regRow["registerusername"] as string;
            Password = regRow["registerpassword"] as string;
            Registrar = regRow["registrar"] as string;
            Realm = regRow["realm"] as string;
            ExpirySeconds = Convert.ToInt32(regRow["expiry"]);
            OriginalExpirySeconds = ExpirySeconds;

            if (Realm == null || Realm.Trim().Length == 0)
            {
                Realm = Registrar;
            }

            Contact = SIPURI.ParseSIPURI(regRow["contact"] as string);

            Contact.Parameters.Set(CONTACT_ID_KEY, Crypto.Crypto.GetRandomInt(6).ToString());
        }

        public string ToXML()
        {
            string nextRegister = null;

            if (LastRegisterTime != DateTime.MinValue)
            {
                if (DateTime.Now.Subtract(LastRegisterTime).TotalSeconds > Int32.MaxValue || DateTime.Now.Subtract(LastRegisterTime).TotalSeconds < Int32.MinValue)
                {
                    nextRegister = "error";
                }
                else
                {
                    double nextRegisterSec = ExpirySeconds - Convert.ToInt32(DateTime.Now.Subtract(LastRegisterTime).TotalSeconds) - SIPRegistrationAgent.REGISTRATION_HEAD_TIME;
                    nextRegister = (nextRegisterSec <= 0) ? "expired" : nextRegisterSec.ToString() + "s";
                }
            }
            
            string regXML =
               "<registration>" +
               " <registrationid>" + RegistrationId + "</registrationid>" +
               " <owner>" + Owner + "</owner>" +
               " <registered>" + Registered + "</registered>" +
               " <regusername>" + Username + "</regusername>" +
               " <regpassword>" + Password + "</regpassword>" +
               " <registrar>" + Registrar + "</registrar>" +
               " <realm>" + Realm + "</realm>" +
               " <contact>" + SafeXML.MakeSafeXML(Contact.ToString()) + "</contact>" +
               " <contactslist>";

            if (ContactsList != null && ContactsList.Count > 0)
            {
                foreach (SIPContactHeader contactHeader in ContactsList)
                {

                    //string contactURI = contactHeader.ContactURI.URITransport + ":";
                    //contactURI += (contactHeader.ContactURI.User != null) ? contactHeader.ContactURI.User + "@" : null;
                    //contactURI += contactHeader.ContactURI.Host;
                    //contactURI += (contactHeader.Expires != -1) ? ";expires=" + contactHeader.Expires : ";expires=" + SIPRegistrationAgent.DEFAULT_REGISTER_EXPIRY;

                    if (contactHeader.ContactURI.Parameters.Get(CONTACT_ID_KEY) == Contact.Parameters.Get(CONTACT_ID_KEY))
                    {
                        regXML += "<contact owner='true'>";
                        regXML += "<contacturi>" + contactHeader.ContactURI.ToString() + "</contacturi>";
                        regXML += "<expires>" + nextRegister + "</expires>";
                        regXML += "<lastregistertime>" + LastRegisterTime.ToString("dd MMM yyyy HH:mm:ss") + "</lastregistertime>";
                        regXML += "</contact>";
                    }
                    else
                    {
                        regXML += "<contact>" + " <contacturi>" + contactHeader.ContactURI.ToString() + "</contacturi>";

                        if (contactHeader.Expires <= 0)
                        {
                            regXML += "<expires>expired</expires>";
                        }
                        else
                        {
                            regXML += "<expires>" + contactHeader.Expires + "</expires>";
                        }

                        regXML += "</contact>";
                    }
                }
            }
              
               regXML += 
               " </contactslist>" +
               "</registration>";

               //logger.Debug(regXML);

            return regXML;
        }
    }
    
    public class SIPRegistrationAgent
	{
        //public const int DNS_RESOLUTION_TIMEOUT = 5000;         // Timeout for resolving DNS hosts in milliseconds.
        public const string SIP_USERAGENT_STRING = "www.CNSIPSwitch.com";
        //public const int DEFAULT_REGISTER_EXPIRY = 3600;
        public const int REGISTRATION_RENEWAL_PERIOD = 3000;    // Time in milliseconds between the registration agent checking registrations.
        public const int REGISTRATION_HEAD_TIME = 30;           // Time in seconds to go to next registration to initate.
        public const int REGISTER_RETRY_INTERVAL = 64;          // Minimum number of seconds between consecutive register requests in the event of failures or timeouts.
        public const int REGISTER_FAILEDDNS_RETRY_INTERVAL = 120; // Minimum number of seconds between consecutive register requests in the event of a DNS failure resolving the registrar server.
        public const int REGISTER_CHECKTIME_THRESHOLD = 3;      // Time the user registration checks should be taking less than. If exceeded a log message is produced.

        private static ILog logger = log4net.LogManager.GetLogger("sipproxy");

		private StorageTypes m_storageType;
		private string m_dbConnStr;
		private StorageLayer m_storageLayer;
         
        private bool m_sendRegisters = true;    // While true the register agent thread will send out register requests to maintain it's registrations.

        private static List<UserRegistration> m_userRegistrations = new List<UserRegistration>();                                 // Keeps track of the all the registrations being maintained.
        private static Dictionary<string, SIPRequest> m_requestsByCallId = new Dictionary<string, SIPRequest>();                  // Provides a lookup mechanism to associate server responses with the correct call.
        private static Dictionary<string, UserRegistration> m_registrationsByCallId = new Dictionary<string, UserRegistration>(); // Provides a lookup mechanism to associate server responses with the correct call.
        private static Dictionary<Guid, UserRegistration> m_registrationsById = new Dictionary<Guid, UserRegistration>();         // Provides a lookup mechanism to enable querying and deletion of specifc registration instances.

        public static event ProxyLogDelegate StatefulProxyLogEvent;

        public SIPRegistrationAgent(
            StorageTypes storageType, 
            string dbConnStr)
		{
			m_storageType = storageType;
			m_dbConnStr = dbConnStr;
            m_storageLayer = new StorageLayer(m_storageType, m_dbConnStr);
        }

		public void Start()
		{
            logger.Debug("SIPRegistrationAgent thread started.");

            Thread regThread = new Thread(new ThreadStart(MonitorRegistrations));
	        regThread.Start();	
		}

        public static void AddRegistration(UserRegistration userRegistration)
        {
            logger.Debug("AddRegistration for " + userRegistration.Username + ", expiry " + userRegistration.ExpirySeconds + ".");

            lock (m_userRegistrations)
            {
                m_userRegistrations.Add(userRegistration);
            }

            lock (m_registrationsById)
            {
                m_registrationsById.Add(userRegistration.RegistrationId, userRegistration);
            }
        }

        public static void DeleteRegistration(Guid registrationId)
        {
            try
            {
                logger.Debug("DeleteRegistration for " + registrationId + ".");

                UserRegistration delReg = m_registrationsById[registrationId];

                lock (m_userRegistrations)
                {
                    m_userRegistrations.Remove(delReg);
                }

                lock (m_registrationsById)
                {
                    m_registrationsById.Remove(delReg.RegistrationId);
                }

                delReg.DeleteRequired = true;
            }
            catch (Exception excp)
            {
                logger.Error("Exception DeleteRegistration. " + excp.Message);
            }
        }

        public static List<UserRegistration> GetOwnerRegistrations(string owner)
        {
            try
            {
                List<UserRegistration> ownerRegs = new List<UserRegistration>();

                foreach (UserRegistration currReg in m_userRegistrations)
                {
                    if (currReg.Owner == owner)
                    {
                        ownerRegs.Add(currReg);
                    }
                }

                return ownerRegs;
            }
            catch (Exception excp)
            {
                logger.Error("Exception GetOwnerRegistrations. " + excp.Message);
                throw excp;
            }
        }

        public static UserRegistration Get(Guid registrationId)
        {
            foreach (UserRegistration currReg in m_userRegistrations)
            {
                if (currReg.RegistrationId == registrationId)
                {
                    return currReg;
                }
            }

            return null;
        }

		/// <summary>
		/// Retrieve a list of accounts that the agent will register for from the database and then monitor them and any additional ones inserte.
		/// </summary>
		public void MonitorRegistrations()
		{
			try
			{
                //string selectSQL = "select * from registrations";

                //DataSet regSet = m_storageLayer.GetDataSet(selectSQL);

                //if (regSet != null && regSet.Tables[0].Rows.Count > 0)
                //{
                //    foreach (DataRow regRow in regSet.Tables[0].Rows)
                //    {
                //        try
                //        {
                //            UserRegistration userReg = new UserRegistration(regRow);

                //            lock (m_userRegistrations)
                //            {
                //                m_userRegistrations.Add(userReg);
                //            }

                //            lock (m_registrationsById)
                //            {
                //                m_registrationsById.Add(userReg.RegistrationId, userReg);
                //            }
                //        }
                //        catch (Exception regCreateExcp)
                //        {
                //            logger.Error("Exception MonitorRegistrations creating UserRegistration from DB record. " + regCreateExcp.Message);
                //        }
                //    }
                //}

                while (m_sendRegisters)
				{
                    List<UserRegistration> deleteReqdReg = new List<UserRegistration>();    // Will record any registrations that have been marked for deletion.

                    //FireProxyLogEvent(new ProxyMonitorEvent(ProxyServerTypesEnum.RegisterAgent, ProxyEventTypesEnum.Timing, "Commencing user registrations check.", null));
                    DateTime startCheckTime = DateTime.Now;
                    //Console.WriteLine("Commencing user registrations check.");

                    lock (m_userRegistrations)
                    {
                        foreach (UserRegistration userReg in m_userRegistrations)
                        {
                            try
                            {
                                //FireProxyLogEvent(new ProxyMonitorEvent(ProxyServerTypesEnum.RegisterAgent, ProxyEventTypesEnum.ContactRegisterInProgress, "Checking user registration " + userReg.Username + " at " + userReg.Registrar + ".", userReg.Owner));
                                //Console.WriteLine("Checking user registration " + userReg.Username + " at " + userReg.Registrar + ".");

                                // Check to see if this registration is marked for deletion.
                                if (userReg.DeleteRequired)
                                {
                                    deleteReqdReg.Add(userReg);
                                    continue;
                                }

                                if (!userReg.Registered || DateTime.Now.Subtract(userReg.LastRegisterTime).TotalSeconds >= (userReg.ExpirySeconds - REGISTRATION_HEAD_TIME))
                                {
                                    if (DateTime.Now.Subtract(userReg.LastRegisterRequestSent).TotalSeconds >= REGISTER_RETRY_INTERVAL)
                                    {
                                        if (userReg.CallId == null)
                                        {
                                            userReg.CallId = Crypto.Crypto.GetRandomInt().ToString() + "@" + SIPTransport.GetTransportContact(null).Address.ToString();
                                        }

                                        // Check DNS each registration.
                                        userReg.RegistrarIPEndPoint = SIPURI.GetURIEndPoint(userReg.Registrar, true);

                                        if (userReg.RegistrarIPEndPoint == null)
                                        {
                                            logger.Warn("Could not resolve " + userReg.Registrar + ".");
                                            FireProxyLogEvent(new ProxyMonitorEvent(ProxyServerTypesEnum.RegisterAgent, ProxyEventTypesEnum.Timing, "DNS resolution timeout for " + userReg.Registrar + " after " + SIPURI.DNS_RESOLUTION_TIMEOUT + "s.", userReg.Owner));
                                            
                                            // For SIP Registrars that can't be found in DNS only retry once every minute.
                                            userReg.LastRegisterRequestSent = DateTime.Now.AddSeconds(REGISTER_FAILEDDNS_RETRY_INTERVAL);
                                        }
                                        else
                                        {
                                            userReg.CSeq = userReg.CSeq + 1;

                                            //FireProxyLogEvent(new ProxyMonitorEvent(ProxyServerTypesEnum.RegisterAgent, ProxyEventTypesEnum.ContactRegisterInProgress, "Initiating registration for " + userReg.Username + " on " + userReg.Registrar + ".", userReg.Owner));
                                            SIPRequest regRequest = GetRegistrationRequest(userReg, SIPTransport.GetTransportContact(userReg.RegistrarIPEndPoint));

                                            lock (m_registrationsByCallId)
                                            {
                                                if (!m_registrationsByCallId.ContainsKey(userReg.CallId))
                                                {
                                                    m_registrationsByCallId.Add(userReg.CallId, userReg);
                                                }

                                                if (m_requestsByCallId.ContainsKey(userReg.CallId))
                                                {
                                                    m_requestsByCallId.Remove(userReg.CallId);
                                                }
                                                m_requestsByCallId.Add(userReg.CallId, regRequest);
                                            }

                                            userReg.AuthRequestSent = false;
                                            userReg.LastRegisterRequestSent = DateTime.Now;

                                            SIPTransaction regTransaction = new SIPTransaction(regRequest);
                                            regTransaction.TransactionFinalResponseReceived += new SIPTransactionResponseReceived(ServerResponseReceived);
                                            regTransaction.TransactionTimedOut += new SIPTransactionTimedOut(regTransaction_TransactionTimedOut);

                                            logger.Debug("Sending REGISTER request for " + regRequest.Header.To.ToUserField.ToString() + " to " + IPSocket.GetSocketString(userReg.RegistrarIPEndPoint) + ".");

                                            //SIPTransport.SendRequest(userReg.RegistrarIPEndPoint, regRequest);
                                            regTransaction.RemoteEndPoint = userReg.RegistrarIPEndPoint;
                                            SIPTransport.SendSIPReliable(regTransaction);
                                        }
                                    }
                                }
                            }
                            catch (Exception regExcp)
                            {
                                logger.Error("Exception MonitorRegistrations attempting register. " + regExcp.Message);
                            }
                        }
                    }

                    #region Remove any registrations in the remove list.

                    foreach (UserRegistration delReg in deleteReqdReg)
                    {
                        logger.Debug("Deleting registration belonging to " + delReg.Owner + " for " + delReg.Registrar + " and " + delReg.Username + ".");

                        foreach (SIPContactHeader delContactHeader in delReg.ContactsList)
                        {
                            FireProxyLogEvent(new ProxyMonitorEvent(ProxyServerTypesEnum.RegisterAgent, ProxyEventTypesEnum.ContactRemoval, "Registration deletion requested, removing contact " + delContactHeader.ContactURI.ToString() + ".", delReg.Owner));
                            SendContactRemovalRequest(delReg.RegistrationId, delContactHeader.ContactURI.ToString());
                        }

                        lock (m_userRegistrations)
                        {
                            m_userRegistrations.Remove(delReg);
                        }

                        lock (m_registrationsById)
                        {
                            m_registrationsById.Remove(delReg.RegistrationId);
                        }

                        // Remove any in progress registrations.
                        /*if (m_registrationsByCallId.ContainsValue(delReg))
                        {
                            string delCallId = null;
                            foreach (KeyValuePair<string, UserRegistration> callReg in m_registrationsByCallId)
                            {
                                if (callReg.Value == delReg)
                                {
                                    delCallId = callReg.Key;
                                    break;
                                }
                            }

                            if (delCallId != null)
                            {
                                lock (m_registrationsByCallId)
                                {
                                    m_registrationsByCallId.Remove(delCallId);
                                }
                            }
                        }*/
                    }

                    #endregion

                    //Console.WriteLine("Registration agent check complete.");

                    //FireProxyLogEvent(new ProxyMonitorEvent(ProxyServerTypesEnum.RegisterAgent, ProxyEventTypesEnum.Timing, "Completed user registrations check.", null));
                    double userRegCheckTime = DateTime.Now.Subtract(startCheckTime).TotalSeconds;
                    if (userRegCheckTime > REGISTER_CHECKTIME_THRESHOLD)
                    {
                        Console.WriteLine("User registrations check time took " + userRegCheckTime.ToString("0.##") + "s.");
                        FireProxyLogEvent(new ProxyMonitorEvent(ProxyServerTypesEnum.RegisterAgent, ProxyEventTypesEnum.Timing, "User registrations check time took " + userRegCheckTime.ToString("0.##") + "s.", null));
                    }

                    Thread.Sleep(REGISTRATION_RENEWAL_PERIOD);
				}
			}
			catch(Exception excp)
			{
				logger.Error("Exception MonitorRegistrations. " + excp.Message);

			}
		}

        void regTransaction_TransactionTimedOut(SIPTransaction sipTransaction)
        {
            try
            {
                SIPRequest sipRequest = sipTransaction.InitialRequest;
                UserRegistration userReg = null;

                if (m_registrationsByCallId.ContainsKey(sipRequest.Header.CallId))
                {
                    userReg = m_registrationsByCallId[sipRequest.Header.CallId];
                   userReg.LastRegisterResponseRecevied = DateTime.Now;
                }

                if (userReg != null)
                {
                    logger.Warn("Registration timed out for for " + userReg.Username + " and " + userReg.Registrar + " owner " + userReg.Owner + ".");
                    FireProxyLogEvent(new ProxyMonitorEvent(ProxyServerTypesEnum.RegisterAgent, ProxyEventTypesEnum.ContactRegisterFailed, "Registration timed out for " + userReg.Username + " on " + userReg.Registrar + ".", userReg.Owner));
                }
                else
                {
                    logger.Error("Registration request timed out to " + sipTransaction.InitialRequestEndPoint.ToString() + ".");
                }
            }
            catch (Exception excp)
            {
                logger.Error("Exception regTransaction_TransactionTimedOut. " + excp.Message);
            }
        }

        public static void ServerResponseReceived(IPEndPoint localEndPoint, IPEndPoint remoteEndPoint, SIPTransaction sipTransaction, SIPResponse sipResponse)
        {
            try
            {                
                if (sipResponse.Status == SIPResponseStatusCodesEnum.ProxyAuthenticationRequired || sipResponse.Status == SIPResponseStatusCodesEnum.Unauthorised)
                {
                    logger.Debug(sipResponse.Status + " response received from " + IPSocket.GetSocketString(remoteEndPoint) + ".");
   
                    if (m_registrationsByCallId.ContainsKey(sipResponse.Header.CallId))
                    {
                        UserRegistration userReg = m_registrationsByCallId[sipResponse.Header.CallId];
                        userReg.LastRegisterResponseRecevied = DateTime.Now;

                        if (!userReg.AuthRequestSent)
                        {
                            #region Responding to server authentication request.

                            string nonce = sipResponse.Header.AuthenticationHeader.AuthRequest.Nonce;
                            string realm = sipResponse.Header.AuthenticationHeader.AuthRequest.Realm;
                            AuthorizationRequest authRequest = new AuthorizationRequest(realm, userReg.Username, userReg.Password, "sip:" + userReg.Registrar, nonce, SIPMethodsEnum.REGISTER.ToString());
                           
                            logger.Debug("Authenticating registration to " + userReg.Registrar + " for " + userReg.Username + ".");

                            userReg.CSeq = userReg.CSeq + 1;

                            //FireProxyLogEvent(new ProxyMonitorEvent(ProxyServerTypesEnum.StatefulProxy, ProxyEventTypesEnum.NewMessage, "No dialplan was found, please create account.", username));
                            //SIPRequest regRequest = GetRegistrationRequest(userReg, SIPTransport.GetTransportContact(userReg.RegistrarIPEndPoint));
                            SIPRequest regRequest = m_requestsByCallId[sipResponse.Header.CallId];
                            regRequest.Header.Via.TopViaHeader.Branch = CallProperties.CreateBranchId();
                            regRequest.Header.From.FromTag = CallProperties.CreateNewTag();
                            regRequest.Header.To.ToTag = null;
                            regRequest.Header.AuthenticationHeader = new SIPAuthenticationHeader(authRequest);
                            regRequest.Header.AuthenticationHeader.AuthRequest.Response = authRequest.Digest;

                            if (sipResponse.Header.WWWAuthenticate)
                            {
                                regRequest.Header.Authorize = true;
                            }
                            else if (sipResponse.Header.ProxyAuthenticate)
                            {
                                regRequest.Header.ProxyAuthorization = true;
                            }
                            else if (sipResponse.Header.Authorize)
                            {
                                regRequest.Header.Authorize = true;
                            }
                            else
                            {
                                regRequest.Header.ProxyAuthorization = true;
                            }

                            userReg.AuthRequestSent = true;

                            // If this request has been marked for deletion send a 0 expiry time.
                            if (userReg.DeleteRequired)
                            {
                                regRequest.Header.Expires = 0;
                            }

                            SIPTransaction regAuthTransaction = new SIPTransaction(regRequest);
                            regAuthTransaction.TransactionFinalResponseReceived += new SIPTransactionResponseReceived(AuthResponseReceived);

                            //logger.Debug(regRequest.ToString());

                            SIPTransport.SendRequest(userReg.RegistrarIPEndPoint, regRequest);

                            #endregion
                        }
                        else
                        {
                            logger.Warn("Registration failed for " + userReg.Username + " and " + userReg.Registrar + " owner " + userReg.Owner + ".");
                            FireProxyLogEvent(new ProxyMonitorEvent(ProxyServerTypesEnum.RegisterAgent, ProxyEventTypesEnum.ContactRegisterFailed, "Registration failed for " + userReg.Username + " on " + userReg.Registrar + ".", userReg.Owner));
                        }
                    }
                    else
                    {
                        logger.Warn("An " + sipResponse.Status + " response was received for an unknown call.");
                    }
                }
                else if (sipResponse.Status == SIPResponseStatusCodesEnum.Ok)
                {
                    OkResponseReceived(sipTransaction, remoteEndPoint, sipResponse);
                }
                else
                {
                    // Reset registration expiry time.
                    if (m_registrationsByCallId.ContainsKey(sipResponse.Header.CallId))
                    {
                        UserRegistration userReg = m_registrationsByCallId[sipResponse.Header.CallId];
                        userReg.LastRegisterResponseRecevied = DateTime.Now;
                        userReg.ExpirySeconds = userReg.OriginalExpirySeconds;

                        FireProxyLogEvent(new ProxyMonitorEvent(ProxyServerTypesEnum.RegisterAgent, ProxyEventTypesEnum.ContactRegisterFailed, "Registration failed with " + sipResponse.Status + " for " + userReg.Username + " on " + userReg.Registrar + ".", userReg.Owner));
                    }

                    logger.Debug("Unknown response " + sipResponse.Status  + " received from server in response to REGISTER.");
                }
            }
            catch (Exception excp)
            {
                logger.Error("Exception SIPRegistrationAgent ResponseReceived. " + excp.Message);
            }
        }

        private static void AuthResponseReceived(IPEndPoint localEndPoint, IPEndPoint remoteEndPoint, SIPTransaction sipTransaction, SIPResponse sipResponse)
        {
            try
            {
                if (m_registrationsByCallId.ContainsKey(sipResponse.Header.CallId))
                {
                    UserRegistration userReg = m_registrationsByCallId[sipResponse.Header.CallId];
                    userReg.LastRegisterResponseRecevied = DateTime.Now;
                    
                    if (sipResponse.Status == SIPResponseStatusCodesEnum.Ok)
                    {
                        OkResponseReceived(sipTransaction, remoteEndPoint, sipResponse);
                    }
                    else
                    {
                        userReg.Registered = false;

                        logger.Warn("Registration failed for " + userReg.Username + " and " + userReg.Registrar + " owner " + userReg.Owner + ".");
                        FireProxyLogEvent(new ProxyMonitorEvent(ProxyServerTypesEnum.RegisterAgent, ProxyEventTypesEnum.ContactRegisterFailed, "Registration failed for " + userReg.Username + " on " + userReg.Registrar + ".", userReg.Owner));
                    }
                }
            }
            catch (Exception excp)
            {
                logger.Error("Exception SIPRegistrationAgent FailedAuthResponseReceived. " + excp.Message);
            }
        }

        private static void OkResponseReceived(SIPTransaction sipTransaction, IPEndPoint remoteEndPoint, SIPResponse sipResponse)
        {
            try
            {
                if (m_registrationsByCallId.ContainsKey(sipResponse.Header.CallId))
                {
                    UserRegistration userReg = m_registrationsByCallId[sipResponse.Header.CallId];

                    logger.Debug(userReg.Username + " successfully registered to " + userReg.Registrar + " for " + userReg.Contact.ToString() + " owner " + userReg.Owner + ".");

                    userReg.Registered = true;
                    userReg.LastRegisterTime = DateTime.Now;

                    // Record contacts list.
                    userReg.ContactsList = sipResponse.Header.Contact;

                    // Find the contact in the list that matches the one being maintained by this agent in order to determine the expiry value.
                    if (userReg.ContactsList != null && userReg.ContactsList.Count > 0)
                    {
                        foreach (SIPContactHeader contactHeader in userReg.ContactsList)
                        {
                            //logger.Debug("comparing: " + contactHeader.ContactURI.ToString() + " to " + userReg.Contact.ToString() + ".");
                            if (contactHeader.ContactURI.Parameters.Get(UserRegistration.CONTACT_ID_KEY) == userReg.Contact.Parameters.Get(UserRegistration.CONTACT_ID_KEY))
                            {
                                if (contactHeader.Expires != -1 && contactHeader.Expires < userReg.ExpirySeconds)
                                {
                                    userReg.ExpirySeconds = contactHeader.Expires;
                                }

                                break;
                            }
                        }
                    }

                    FireProxyLogEvent(new ProxyMonitorEvent(ProxyServerTypesEnum.RegisterAgent, ProxyEventTypesEnum.ContactRegistered, "Contact successfully registered for " + userReg.Username + " on " + userReg.Registrar + ", expiry " + userReg.ExpirySeconds + "s.", userReg.Owner));
                }
                else
                {
                    logger.Warn("An Ok response was received for an unknown call.");
                }
            }
            catch (Exception excp)
            {
                logger.Error("Exception SIPRegistrationAgent ResponseReceived. " + excp.Message);
            }
        }

		public static SIPRequest GetRegistrationRequest(UserRegistration userRegistration, IPEndPoint proxyIPEndPoint)
		{	
			try
			{
                string realm = (userRegistration.Realm != null && userRegistration.Realm.Trim().Length > 0) ? userRegistration.Realm : userRegistration.Registrar;

                SIPURI registerURI = SIPURI.ParseSIPURI("sip:" + realm);
                SIPURI regUserURI = SIPURI.ParseSIPURI("sip:" + userRegistration.Username + "@" + realm);

                SIPFromHeader fromHeader = new SIPFromHeader(null, regUserURI, CallProperties.CreateNewTag());
                SIPToHeader toHeader = new SIPToHeader(null, regUserURI, null);
				SIPContactHeader contactHeader = new SIPContactHeader(null, userRegistration.Contact);
                string callId = userRegistration.CallId;
                int cseq = userRegistration.CSeq;	
				
                SIPRequest registerRequest = new SIPRequest(SIPMethodsEnum.REGISTER, registerURI);

				SIPHeader header = new SIPHeader(contactHeader, fromHeader, toHeader, cseq, callId);
				header.CSeqMethod = SIPMethodsEnum.REGISTER.ToString();
                header.UserAgent = SIP_USERAGENT_STRING;
                header.Expires = userRegistration.OriginalExpirySeconds;

                string branchId = SIPConstants.SIP_BRANCH_MAGICCOOKIE + Crypto.Crypto.GetRandomInt().ToString();
                SIPViaHeader viaHeader = new SIPViaHeader(proxyIPEndPoint.Address.ToString(), proxyIPEndPoint.Port, branchId);
				header.Via.PushViaHeader(viaHeader);

                registerRequest.Header = header;
                return registerRequest;
			}
			catch(Exception excp)
			{
                logger.Error("Exception GetRegistrationRequest. " + excp.Message);
				throw excp;
			}
		}

        public static void SendContactRemovalRequest(Guid registrationId, string contactToRemove)
        {
            try
            {
                UserRegistration userReg = m_registrationsById[registrationId];

                logger.Debug("Submitting contact removal request for " + userReg.Username + " and " + contactToRemove + ".");

                userReg.CSeq = userReg.CSeq + 1;

                SIPRequest regRequest = GetRegistrationRequest(userReg, SIPTransport.GetTransportContact(userReg.RegistrarIPEndPoint));
                regRequest.Header.Expires = 0;
                regRequest.Header.Contact = SIPContactHeader.ParseContactHeader("<" + contactToRemove + ">");
                regRequest.Header.Contact[0].Expires = 0;

                if (!m_registrationsByCallId.ContainsKey(userReg.CallId))
                {
                    m_registrationsByCallId.Add(userReg.CallId, userReg);
                }

                if (m_requestsByCallId.ContainsKey(userReg.CallId))
                {
                    m_requestsByCallId.Remove(userReg.CallId);
                }
                m_requestsByCallId.Add(userReg.CallId, regRequest);

                userReg.AuthRequestSent = false;
                userReg.LastRegisterRequestSent = DateTime.Now;

                SIPTransaction regTransaction = new SIPTransaction(regRequest);
                regTransaction.TransactionFinalResponseReceived += new SIPTransactionResponseReceived(ServerResponseReceived);

                //logger.Debug("Sending REGISTER request (for removal) for " + regRequest.Header.From.FromUserField.ToString() + " to " + IPSocket.GetSocketString(userReg.RegistrarIPEndPoint) + ".");

                SIPTransport.SendRequest(userReg.RegistrarIPEndPoint, regRequest);
            }
            catch (Exception excp)
            {
                logger.Error("Exception SendContactRemovalRequest. " + excp.Message);
            }
        }

        private static void FireProxyLogEvent(ProxyMonitorEvent monitorEvent)
        {
            if (StatefulProxyLogEvent != null)
            {
                try
                {
                    StatefulProxyLogEvent(monitorEvent);
                }
                catch (Exception excp)
                {
                    logger.Error("Exception FireProxyLogEvent SIPRegistrationAgent. " + excp.Message);
                }
            }
        }
	}
}
